#pragma once

#include "utility.hpp"

namespace zuck
{
	template<typename T>
	class default_deleter {
	public:
		constexpr default_deleter()noexcept {}

		inline void operator()(T* data)const noexcept
		{
			if (data) {
				delete data;
			}
		}
	};


	template<typename T, typename _Del = default_deleter<T>>
	class unique_ptr {
		using _Self = unique_ptr<T>;
	public:
		using value_type      = T;
		using pointer         = T*;
		using const_pointer   = const T*;
		using reference       = T&;
		using const_reference = const T&;
		using deleter         = _Del;
	private:
		pointer _m_ptr;
		deleter _m_del;
	public:
		explicit unique_ptr(const T* const _ptr = null)
			: _m_ptr(_ptr) {}

		explicit unique_ptr(const T& val)
			: _m_ptr(new T(val)) {}

		explicit unique_ptr(T&& val)
			: _m_ptr(new T(zuck::move(val))) {}

		template<typename ...Args>
		unique_ptr(Args&&... args)
			: _m_ptr(new T(zuck::forward<Args>(args)...)) {}

		inline unique_ptr(unique_ptr&& other)noexcept
			: _m_ptr(zuck::exchange(other._m_ptr, null)) {}

		inline ~unique_ptr()
		{
			_m_del(_m_ptr);
		}

		inline void reset(T* _ptr = null)
		{
			if (!_ptr) {
			    __destroy(_m_ptr);
				_m_ptr = _ptr;
				return;
			}
			__destroy(_m_ptr);
			_m_ptr = _ptr;
		}

		inline void swap(unique_ptr& other)noexcept
		{
			if (*this != other) {
				auto _Tmp = static_cast<_Self&&>(other);
				other	  = static_cast<_Self&&>(*this);
				*this	  = static_cast<_Self&&>(_Tmp);
			}
		}

		[[nodiscard]] inline pointer release(T* _new = null)
		{
			T* _Old = zuck::exchange(_m_ptr, _new);
			return _Old;
		}

		[[nodiscard]] inline pointer get()noexcept { return _m_ptr; }

		[[nodiscard]] inline const_pointer get()const noexcept { return _m_ptr; }

		[[nodiscard]] inline pointer operator->()noexcept { return _m_ptr; }

		[[nodiscard]] inline const_pointer operator->()const noexcept { return _m_ptr; }

		[[nodiscard]] inline reference operator*()noexcept { return *_m_ptr; }

		[[nodiscard]] inline const_reference operator*()const noexcept { return *_m_ptr; }

		[[nodiscard]] inline bool operator==(const T* const other)const noexcept
		{ return static_cast<bool>(_m_ptr == other); }

		[[nodiscard]] inline bool operator==(const unique_ptr<T>& other)const noexcept
		{ return static_cast<bool>(_m_ptr == other._m_ptr); }

		[[nodiscard]] inline bool operator!=(T* const other)const noexcept
		{ return static_cast<bool>(_m_ptr != other); }

		[[nodiscard]] inline bool operator!=(const unique_ptr<T>& other)const noexcept
		{ return static_cast<bool>(_m_ptr != other._m_ptr); }

		unique_ptr(unique_ptr const&) = delete;
		unique_ptr& operator=(unique_ptr const&) = delete;

    private:
        inline void __destroy()
		{
            _m_del(_m_ptr);
            _m_ptr = null;
        }

	};


	template<typename T, typename deleter>
	unique_ptr(T*) -> unique_ptr<T, deleter>;

	template<typename T, typename deleter>
	unique_ptr(T&) -> unique_ptr<T, deleter>;

	template<typename T, typename deleter>
	unique_ptr(T&&) -> unique_ptr<T, deleter>;

	template<typename T, typename deleter>
	unique_ptr(...) -> unique_ptr<T, deleter>;
}

